<?php
	/**
	 * Elgg CAS authentication
	 * 
	 * @package cas_auth
	 * @license http://www.gnu.org/licenses/gpl.html
	 * @author Xavier Roussel <xavier.roussel@uvsq.fr>
	 * @copyright UVSQ 2008
	 * @link http://www.uvsq.fr
	 */

	// Include main cas lib
	include_once 'cas/CAS.php';
	$casInitialized = false;
	
	/**
	 * CAS Authentication init
	 * 
	 */
	function cas_auth_init()
	{
		// global config
		global $CONFIG;
		// plugin config
		$config = find_plugin_settings('cas_auth');
		// todo : send message to user
		if (!$config) return false;

		// CAS auth required
		if ( $_REQUEST['authModeReq'] == 'CAS' && !isset($_REQUEST['ticket']) )
		{	
			createCas();
		}
		// CAS auth done
		if ( $_REQUEST['authModeReq'] == 'CAS' && isset($_REQUEST['ticket']) )
		{
			// Check CAS auth the CAS way just in case
			if ( checkCas() ) {
				$_SESSION['loggedWithCAS'] = true;
				if(ldapAuthenticate( getUserCas() )) {
					system_message(elgg_echo('loginok'));
				}
				else register_error(elgg_echo('loginerror'));
			}
		}
		// The CAS ticket is lost, log out
		if ( $_SESSION['loggedWithCAS'] && !checkCas() ) {
			$_SESSION['loggedWithCAS'] = false;
			forward($CONFIG->url.'/action/logout');
		}
	}
	
	// Register the initialisation function
	register_elgg_event_handler('init','system','cas_auth_init');
	// Register CAS logout to main logout only if user logged with CAS
	if ( $_SESSION['loggedWithCAS'] ) {
		register_elgg_event_handler('logout', 'user', 'logoutCas');
	}

	/**
	 * CAS client initialization
	 * 
	 */
	function initCas() {
		if (!$GLOBALS[casInitialized]) {
			$config = find_plugin_settings('cas_auth');
			phpCAS::client(CAS_VERSION_2_0, $config->casurl, (int) $config->casport , $config->casuri );
			$GLOBALS[casInitialized] = true;
		}	
	}

	/**
	 * Force authentication
	 * 
	 */
	function createCas() {
		initCas();
		phpCAS::forceAuthentication();
	}

	/**
	 * Check auth
	 * 
	 * @return boolean
	 */
	function checkCas() {		
		initCas();
		if (phpCAS::checkAuthentication()) {
			return true;
		}
		else return false;
	}

	/**
	 * Get CAS user
	 * 
	 * @return string name of the user
	 */
	function getUserCas() {
		return phpCAS::getUser();
	}

	/**
	 * CAS logout
	 * 
	 */
	function logoutCas() {
		global $CONFIG;
		initCas();
		phpCAS::logout($CONFIG->url.'/action/logout');	
	}
	
	/**
   * LDAP authentication
   * 
   * @param string $username Go around PAM handler credentials (CAS can't return a password)
   * @return boolean
   */
	function ldapAuthenticate($username)
	{
		// Nothing to do if LDAP module not installed
		if (!function_exists('ldap_connect')) return false;

		// Get configuration settings
		$config = find_plugin_settings('ldap_auth');

		// Nothing to do if not configured
		if (!$config)
		{
			return false;
		}

		if (empty($username)) {
			return false;
		}

		// Perform the authentication
		return ldapCheck($config, $username);
	}
   
  /**
   * Perform an LDAP authentication check
   *
   * @param ElggPlugin $config
   * @param string $username
   * @return boolean
   */
	function ldapCheck($config, $username)
	{
		$host = $config->hostname;

		// No point continuing
		if(empty($host))
		{
			error_log("LDAP error: no host configured.");
			return;
		}
		$port        = $config->port;
		$version     = $config->version;
		$basedn      = $config->basedn;
		$filter_attr = $config->filter_attr;
		$search_attr = $config->search_attr;
		$bind_dn     = $config->ldap_bind_dn;
		$bind_pwd    = $config->ldap_bind_pwd;
		$user_create = $config->user_create;
		$start_tls   = $config->start_tls;

		($user_create == 'on') ? $user_create = true : $user_create = false;
		($start_tls == 'on') ? $start_tls = true : $start_tls = false;

		$port        ? $port        : $port = 389;
		$version     ? $version     : $version = 3;
		$filter_attr ? $filter_attr : $filter_attr = 'uid';
		$basedn      ? $basedn = array_map('trim', explode(':', $basedn)) : $basedn = array();

		if (!empty($search_attr))
		{
			// $search_attr as in "email:email_address, name:name_name";

			$pairs = array_map('trim',explode(',', $search_attr));

			$values = array();

			foreach ($pairs as $pair)
			{
				$parts = array_map('trim', explode(':', $pair));

				$values[$parts[0]] = $parts[1];
			}

			$search_attr = $values;
		}
		else
		{
			$search_attr = array('dn' => 'dn');
		}

		// Create a connection
		if ($ds = ldapConnect($host, $port, $version, $bind_dn, $bind_pwd))
		{
			if ($start_tls and !ldap_start_tls($ds)) return false;

			// Perform a search
			foreach ($basedn as $this_ldap_basedn)
			{
				$ldap_user_info = ldapDoAuth($ds, $this_ldap_basedn, $username, $filter_attr, $search_attr);

				if($ldap_user_info)
				{
					// LDAP login successful
					if ($user = get_user_by_username($username))
					{
						// User exists, login            	        
						return login($user);
					}
					else
					{
						// Valid login but user doesn't exist
						if ($user_create)
						{
							$name  = $ldap_user_info['firstname'];

							if (isset($ldap_user_info['lastname']))
							{
								$name  = $name . " " . $ldap_user_info['lastname'];
							}

							($ldap_user_info['mail']) ? $email = $ldap_user_info['mail'] : $email = null;

							if ($user_guid = register_user($username, 'generic', $name, $email))
							{
								// Success, credentials valid and account has been created                                
								return login(get_user($user_guid));
							}
							else
							{
								register_error(elgg_echo('ldap_auth:no_register'));
								return false;
							}
						}
						else
						{
							register_error(elgg_echo("ldap_auth:no_account"));
							return false;
						}
					}
				}
			}
			// Close the connection
			ldap_close($ds);
			return false;
		}
		else
		{
			return false;
		}
	}
    
	/**
	 * Create an LDAP connection
	 *
	 * @param string $host
	 * @param int $port
	 * @param int $version
	 * @param string $bind_dn
	 * @param string $bind_pwd
	 * @return mixed LDAP link identifier on success, or false on error
	 */
	function ldapConnect($host, $port, $version, $bind_dn, $bind_pwd)
	{
		$ds = @ldap_connect($host, $port);

		@ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $version);
		// Start the LDAP bind process
		$ldapbind = null;

		if ($ds)
		{
			if ($bind_dn != '')
			{
				$ldapbind = @ldap_bind($ds, $bind_dn, $bind_pwd);
			}
			else
			{
				// Anonymous bind
				$ldapbind = @ldap_bind($ds);
			}
		}
		else
		{
			// Unable to connect
			error_log('Unable to connect to the LDAP server: '.ldap_error($ds));
			return false;
		}

		if (!$ldapbind)
		{
			error_log('Unable to bind to the LDAP server with provided credentials: '.ldap_error($ds));
			ldap_close($ds);
			return false;
		}
		return $ds;
	}

	/**
	 * Performs actual LDAP authentication
	 *
	 * @param object $ds LDAP link identifier
	 * @param string $basedn
	 * @param string $username
	 * @param string $filter_attr
	 * @param string $search_attr
	 * @return mixed array with search attributes or false on error
	 */
	function ldapDoAuth($ds, $basedn, $username, $filter_attr, $search_attr)
	{
		$sr = @ldap_search($ds, $basedn, $filter_attr ."=". $username, array_values($search_attr));
		if(!$sr)
		{
			error_log('Unable to perform LDAP search: '.ldap_error($ds));
			return false;
		}

		$entry = ldap_get_entries($ds, $sr);
		if(!$entry or !$entry[0])
		{
			return false; // didn't find username
		}

		// We have a bind, a valid login
		foreach (array_keys($search_attr) as $attr)
		{
			$ldap_user_info[$attr] = $entry[0][$search_attr[$attr]][0];
		}
		return $ldap_user_info;
	}
?>